热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

也就是|下篇_Laravel系列6.4管道过滤器

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Laravel系列6.4管道过滤器相关的知识,希望对你有一定的参考价值。管道过滤器通

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Laravel系列6.4管道过滤器相关的知识,希望对你有一定的参考价值。



管道过滤器

通过之前的三篇文章,我们已经学习完了服务容器相关的内容,可以说,服务容器就是整个 Laravel 框架的灵魂,从启动的第一步开始就是创建容器并且加载所有的服务对象。而说起管道,其实大家也不会太陌生,在程序开发的世界中,管道模式的应用随处可见,同样在 Laravel 框架中,它也是核心一般的存在。甚至可以说,管道和服务容器的组合,才让我们有了一个这样的框架可以使用。


什么是管道


前面说过,管道模式非常常见,为什么这么说呢?


ps -ef | grep php

常见不?经常用吧?这个 Linux 命令就是一个管道命令。前面一条命令的结果交给后面一条命令来执行,就像一条管道一样让这个命令请求的结果向下流动,这就是管道模式的应用。


除了这个你还能想到什么呢?如果你跟过我的 PHP 设计模式系列的话,那么 责任链模式 很明显就是管道模式在 面向对象 语言中的应用呀。







管道模式一般是和过滤器一起使用的,什么是过滤器呢?其实就是我们要处理请求的那些中间方法,比如说上面命令中的 grep ,或者是 wc 、awk 这些的命令。大家其实很快就能发现,在 Laravel 框架中,我们的中间件就是一个个的过滤器。而我们要处理的数据,就是那个 Request 请求对象。


Laravel 中管道的加载应用


还记得我们在服务容器中看到过的一个 sendRequestThroughRouter() 方法吗?另外在最早讲中间件时,我们也讲过这里,我们再来看看它的代码。


protected function sendRequestThroughRouter($request)
    $this->app->instance('request', $request);
    Facade::clearResolvedInstance('request');
    $this->bootstrap();
    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());

在这段代码中,最后返回的那个 Pipeline 对象就是一个管道对象。我们来看看它的这几个方法是什么意思。


public function __construct(Container $container = null)
    $this->container = $container;
public function send($passable)
    $this->passable = $passable;
    return $this;
public function through($pipes)
    $this->pipes = is_array($pipes) ? $pipes : func_get_args();
    return $this;

构造函数、send() 和 through() 方法都比较简单,就是给当前的对象中的属性赋值,这个没什么特别的。不过在 Pipeline 对象中,所有的方法都是会 return 一个 $this ,其实也就是实现了对象的链式调用。


重点在于 then() 方法。


public function then(Closure $destination)
    $pipeline = array_reduce(
        array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
    );
    return $pipeline($this->passable);

这个方法也出乎意料的简单吧?里面只用了一个 array_reduce() ,OK,到这里,你就可以和面试官吹牛了,Laravel 中的管道,或者说中间件,其实最核心的就是这个 array_reduce() 方法。要搞清楚 then() 方法是在干什么,我们就要先搞明白 array_reduce() 是在干嘛。


array_reduce


array_reduce() 这个函数在官方文档的签名是这样的:


array_reduce(array $array, callable $callback, mixed $initial = null): mixed

它的作用是将回调函数 callback 迭代地作用到 array 数组中的每一个单元中,从而将数组简化为单一的值。如果指定了可选参数 initial,该参数将用作处理开始时的初始值,如果数组为空,则会作为最终结果返回。


callback 这个回调函数会有两个参数,分别是 carry 携带上次迭代的返回值,如果迭代是第一次,那么这个值就是 initial 。另一个参数是 item ,也就是数组中的每个值。


看不懂吧?正常,我也看不懂,别慌,看例子。


function sum($carry, $item)
    $carry += $item;
    return $carry;
function product($carry, $item)
    $carry *= $item;
    return $carry;
$a = array(1, 2, 3, 4, 5);
$x = array();
var_dump(array_reduce($a, "sum")); // int(15)
var_dump(array_reduce($a, "product", 10)); // int(1200), because: 10*1*2*3*4*5
var_dump(array_reduce($x, "sum", "No data to reduce")); // string(17) "No data to reduce"

这段代码是官网上的例子。我们定义了一个 sum() 方法用于累加,另外再定义了一个 product() 方法用于阶乘。前两段测试的结果可以看出,通过将第一个数组传递进去,然后调用 sum() 方法,我们完成了累加的功能,输出了一个唯一的结果值。第二段则是增加了第三个参数给了个默认的 10 ,结果就是多乘了一个 10 的累乘结果。而最后一段则是一个空的数组,返回的是 initial 给定的结果。


框架中 array_reduce 的参数


搞清楚了 array_reduce() 我们再回来看看框架源码中给出的参数。第一个参数是使用 array_reverse() 返回之后的 pipes 里面的内容,这个 pipes 是我们通过 through() 方法传递进来的。再回到 Kernel 中,我们会发现这个方法传递进去的参数正是我们框架中加载的中间件 $middleware 成员变量。


之前的 bootstrap() 过程中,我们已经将所有的 app/Http/Kernel.php 中注册的中间件绑定注册到了服务容器中。因此,这个 pipes 数组中,就是我们所有的中间件信息。


接下来第二个参数是调用的一个 carry() 函数,它在 array_reduce() 方法中代表的是 callback 那个回调函数。


protected function carry()
    return function ($stack, $pipe) 
        return function ($passable) use ($stack, $pipe) 
            try 
                if (is_callable($pipe)) 
                    return $pipe($passable, $stack);
                 elseif (! is_object($pipe)) 
                    [$name, $parameters] = $this->parsePipeString($pipe);
                    $pipe = $this->getContainer()->make($name);
                    $parameters = array_merge([$passable, $stack], $parameters);
                 else 
                    $parameters = [$passable, $stack];
                
                $carry = method_exists($pipe, $this->method)
                                ? $pipe->$this->method(...$parameters)
                                : $pipe(...$parameters);
                return $this->handleCarry($carry);
             catch (Throwable $e) 
                return $this->handleException($passable, $e);
            
        ;
    ;

这个方法就复杂许多了。我们一步步的来看。


参数不用多说了吧,stack 是上一次的返回值,pipe 是当前我们要处理的值,也就是当前的中间件对象。在这个回调函数中又调用了一层回调函数,并将这两个值通过 use 传递进去。而在里面的这个回调函数中,我们的参数是 passable 这个变量。这个 passable 又是哪里来的?别急,我们先看这个函数内部的实现,最后会再说到 passable 这个问题。


进入函数内部的 try 代码段中,第一个判断,如果 pipe 是一个回调函数,直接调用它并返回;第二个判断,如果 pipe 不是一个对象而是一个 string 的话,解构 pipe 信息,服务容器 make 它,并且准备好参数;最后一个 else 也就是 pipe 是一个对象,那么将 passable 和 stack 作为它的参数。最后,如果对象都有了,就会统一调用对象的 handle 方法,这个方法名也就是 $this->method 属性定义的方法名。在最底下 $carry 调用对象或者回调函数的执行方法。handle 熟悉不?我们自定义中间件时,要实现的就是这个方法。参考:【Laravel系列3.4】中间件在路由与控制器中的应用 https://mp.weixin.qq.com/s/9340q7F_hKrrxgf4o1LNMw。


最终返回的就是这个 $carry 变量,它是啥?中间件中 return next() 的东西呀,管道中的下一个回调函数。


上面的代码我们是嵌套了两层的回调函数,通过之间的学习,我们知道回调函数是有延迟加载的特性的,也就说,这一堆代码是在我们最终调用这个回调函数的时候才会触发的,那么它是在什么时候调用的呢?


public function then(Closure $destination)
    $pipeline = array_reduce(
        array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
    );
    return $pipeline($this->passable);

没错,then() 方法最后的这个 return 这里,现在知道 passable 是从哪里传递进去的了吧。注意,这个 passable 和最后那个默认 initial 参数,都是我们当前的请求 Request 对象和路由 Route 对象。也就是说,在整个 Laravel 框架中,我们管道中流动的,正是我们的 Request 对象,而最后返回的,则是各个中间件以及控制器处理完成之后的 Response 对象。中间件、控制器甚至路由,其实都是我们管道中的一个个的过滤器,根据我们的条件情况以及业务情况,可以随时中断或者对请求进行处理,这下也就理解了什么我们可以在中间件返回,也可以在路由直接返回页面结果了吧。


好吧,学习一个管道,其实我们又把整个请求响应流程梳理了一遍。收获满满吧!


直接写一个管道应用来测试


直接调试管道可能比较复杂,因为 Laravel 框架加载的内容非常多,不过我们可以自己写一个管道应用来测试,并且可以设置断点来方便地调试。


首先,我们需要定义几个过滤器,也就是我们的中间件啦,不过我们不需要去实现 Laravel 规范的,只需要有 handle() 方法就可以了。


class AddDollar
    public function handle($text, $next)
        return $next("$".$text."$");
    
class AddTime
    public function handle($text, $next)
        $t = $next($text);
        return $t . time();
    
class EmailChange
    public function handle($text, $next)
        return $next(str_replace("@", "#", $text));
    

没有什么特殊的功能,我们过滤掉 Email 中的 @ 符号变成 # 号,这个很多网站有会这样的功能,避免被爬取 Email 地址。另外两个就是增加符号和时间戳。在 AddTime 的处理中,我们使用的是 后置 中间件的功能,也就是在中间件完成处理后再添加内容。这个在中间件相关的课程中我们也已经讲过了。


接下来,就是使用管道来进行处理。


Route::get('pipeline/test1', function()
    $pipes = [
        \\App\\PipelineTest\\EmailChange::class,
        \\App\\PipelineTest\\AddTime::class,
        new \\App\\PipelineTest\\AddDollar(),
        function($text, $next)
            return $next("【".$text."】");
        ,
    ];
    return app(\\Illuminate\\Pipeline\\Pipeline::class)
        ->send("测试内容看看替换Email:zyblog@zyblog.ddd")
        ->through($pipes)
        ->then(function ($text) 
            return $text . "end";
        );
    // $【测试内容看看替换Email:zyblog#zyblog.ddd】$end1630978948
);

在这段测试代码中,我们对 pipes 数组使用了类字符串、实例对象、回调函数三种方式来实现中间件过滤器,可以看到最后的输出结果正是我们想要的内容。


大家可以在这里设置断点然后进入到 Pipeline 中查看这些中间件是如何调用运行的,为什么要使用 array_reverse() 反转中间件的顺序,为什么后置中间件会在最后才去添加数据内容。这一块的调试就留给大家自己来吧!


总结


服务容器、管道(中间件)可以说是 Laravel 框架中最最核心的内容,也可以说整个框架就是建立在这两个模式之下的。对于服务容器的理解,就是要解决类的依赖问题,而对于管道的理解,则是要解决请求和响应的数据流问题。本身我们做 Web 开发,实际上就是在做对请求和响应这两条数据流的各种操作而已。


理解了最核心的两部分内容之后,下篇文章的课程中我们再来看看在 Laravel 中非常常用的 门面 功能是怎样实现的。


参考文档:


Laravel 中的 Pipeline — 管道设计范式 :https://learnku.com/laravel/t/7543/pipeline-pipeline-design-paradigm-in-laravel


Laravel 管道流原理 :https://learnku.com/articles/5206/the-use-of-php-built-in-function-array-reduce-in-laravel


推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 【shell】网络处理:判断IP是否在网段、两个ip是否同网段、IP地址范围、网段包含关系
    本文介绍了使用shell脚本判断IP是否在同一网段、判断IP地址是否在某个范围内、计算IP地址范围、判断网段之间的包含关系的方法和原理。通过对IP和掩码进行与计算,可以判断两个IP是否在同一网段。同时,还提供了一段用于验证IP地址的正则表达式和判断特殊IP地址的方法。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • ECMA262规定typeof操作符的返回值和instanceof的使用方法
    本文介绍了ECMA262规定的typeof操作符对不同类型的变量的返回值,以及instanceof操作符的使用方法。同时还提到了在不同浏览器中对正则表达式应用typeof操作符的返回值的差异。 ... [详细]
  • 本文介绍了Paxos的世界中关于复制日志与状态机的概念和重要性。通过存储日志来实现数据的持久化,并通过日志流来记录数据的变化,而不是直接持久化数据本身。这样做的好处是简化了持久化存储的操作,并且方便多机之间的数据同步。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文讨论了微软的STL容器类是否线程安全。根据MSDN的回答,STL容器类包括vector、deque、list、queue、stack、priority_queue、valarray、map、hash_map、multimap、hash_multimap、set、hash_set、multiset、hash_multiset、basic_string和bitset。对于单个对象来说,多个线程同时读取是安全的。但如果一个线程正在写入一个对象,那么所有的读写操作都需要进行同步。 ... [详细]
author-avatar
爵士723
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有